package com.zhouyu.mini.spring;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: 01195
 * @Date: 2025/9/9 15:31
 * @Description 容器类
 */
public class ZhouyuApplicationContext {

    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    private Map<String, Object> singletonObjects = new HashMap<>();
    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

    // 构造函数
    public ZhouyuApplicationContext(Class<?> configClass) {
        scan(configClass);

        beanPostProcessorList.add(new AopBeanPostProcessor(this));

        // 实例化所有单例Bean
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            BeanDefinition beanDefinition = entry.getValue();
            if ("singleton".equals(beanDefinition.getScope()) && !beanDefinition.isLazy()) {
                Object singletonBean = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName, singletonBean);
            }
        }
    }

    // bean的生命周期
    public Object createBean(String beanName, BeanDefinition beanDefinition) {
        try {
            // 实例化Bean 普通对象 构造方法注入
            Object instance = beanDefinition.getBeanClass().getConstructor().newInstance();

            // 依赖注入 非构造方法注入
            Field[] fields = beanDefinition.getBeanClass().getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    field.setAccessible(true);
                    field.set(instance, getBean(field.getName()));
                }
            }

            if (instance instanceof BeanNameAware beanNameAware) {
                beanNameAware.setBeanName(beanName);
            }

            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                instance = beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
            }

            // 初始化
            if (instance instanceof InitializingBean) {
                ((InitializingBean)instance).afterPropertiesSet();
            }

            // 初始化后 AOP
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                instance = beanPostProcessor.postProcessAfterInitialization(instance, beanName);
            }

            return instance;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private void scan(Class<?> configClass) {
        if (configClass != null && configClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScan = configClass.getAnnotation(ComponentScan.class);
            String path = componentScan.value(); // com.zhouyu.service
            path = path.replace(".", "/"); // com/zhouyu/service

            // 扫描 ASM 反射
            ClassLoader classLoader = this.getClass().getClassLoader();
            URL resource = classLoader.getResource(path);
            File file = new File(resource.getFile());
            for (File listFile : file.listFiles()) {
                String classpath = listFile.getPath();
                classpath = classpath.substring(classpath.indexOf("com"), classpath.indexOf(".class"));
                classpath = classpath.replace("\\", ".");
                try {
                    Class<?> clazz = classLoader.loadClass(classpath);
                    // 判断类上是否有 Component 注解 代表是一个bean
                    if (clazz.isAnnotationPresent(Component.class)) {
                        // 判断类是否是 BeanPostProcessor
                        if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
                            BeanPostProcessor instance = (BeanPostProcessor) clazz.getConstructor().newInstance();
                            beanPostProcessorList.add(instance);
                        }

                        Component component = clazz.getAnnotation(Component.class);
                        String beanName = component.value();
                        if ("".equals(beanName)) {
                            beanName = clazz.getSimpleName();
                        }
                        BeanDefinition beanDefinition = new BeanDefinition();
                        beanDefinition.setBeanClass(clazz);
                        if (clazz.isAnnotationPresent(Scope.class)) {
                            beanDefinition.setScope(clazz.getAnnotation(Scope.class).value());
                        } else {
                            beanDefinition.setScope("singleton");
                        }
                        if (clazz.isAnnotationPresent(Lazy.class)) {
                            beanDefinition.setLazy(clazz.getAnnotation(Lazy.class).value());
                        } else {
                            beanDefinition.setLazy(false);
                        }

                        beanDefinitionMap.put(beanName, beanDefinition);
                    }
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                } catch (InvocationTargetException e) {
                    throw new RuntimeException(e);
                } catch (InstantiationException e) {
                    throw new RuntimeException(e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    public Object getBean(String beanName) {
        if (!beanDefinitionMap.containsKey(beanName)) {
            throw new RuntimeException("beanName " + beanName + " is not exist");
        }
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if ("singleton".equals(beanDefinition.getScope())) {
            Object singletonBean = singletonObjects.get(beanName);
            if (singletonBean == null) {
                singletonBean = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName, singletonBean);
                return singletonBean;
            } else {
                return singletonBean;
            }
        } else if ("prototype".equals(beanDefinition.getScope())) {
            return createBean(beanName, beanDefinition);
        }
        return null;
    }

    public Map<String, BeanDefinition> getBeanDefinitionMap() {
        return beanDefinitionMap;
    }
}
